package com.alibaba.dubbo.remoting.msgcodec;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;

import com.alibaba.dubbo.common.io.Bytes;
import com.alibaba.dubbo.common.io.UnsafeByteArrayOutputStream;

public class DES3Codec {
	public static final String ALGORITHM = "DESede";
	public static final String CIPHER_ALGORITHM = "DESede/CBC/PKCS5Padding";
	private static final int BUFFER_SIZE = 4096;

	public static byte[] decrypt(byte[] data, byte[] key, byte[] iv)
			throws Exception {
		DESedeKeySpec spec = new DESedeKeySpec(key);
		SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
		SecretKey sec = keyFactory.generateSecret(spec);
		Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
		IvParameterSpec IvParameters = new IvParameterSpec(iv);
		cipher.init(Cipher.DECRYPT_MODE, sec, IvParameters);
		return cipher.doFinal(data);
	}

	public static int decrypt(InputStream in, OutputStream out, byte[] key,
			byte[] iv) throws Exception {
		DESedeKeySpec spec = new DESedeKeySpec(key);
		SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
		SecretKey sec = keyFactory.generateSecret(spec);
		Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
		IvParameterSpec IvParameters = new IvParameterSpec(iv);
		cipher.init(Cipher.DECRYPT_MODE, sec, IvParameters);
		byte[] data = new byte[BUFFER_SIZE];
		int len = 0;
		int count = 0;
		int total = in.available();
		while ((len = in.read(data)) > 0) {
			count += len;
			boolean last = count >= total;
			byte[] written = last?cipher.doFinal(data, 0, len):cipher.update(data, 0, len);
			if(written != null){
				out.write(written);
			}
		}
		 
		out.flush();
		return count;
	}

	public static byte[] encrypt(byte[] data, byte[] key, byte[] iv)
			throws Exception {
		DESedeKeySpec spec = new DESedeKeySpec(key);
		SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
		SecretKey sec = keyFactory.generateSecret(spec);
		Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
		IvParameterSpec IvParameters = new IvParameterSpec(iv);
		cipher.init(Cipher.ENCRYPT_MODE, sec, IvParameters);
		return cipher.doFinal(data);
	}

	public static int encrypt(InputStream in, OutputStream out, byte[] key,
			byte[] iv) throws Exception {
		DESedeKeySpec spec = new DESedeKeySpec(key);
		SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
		SecretKey sec = keyFactory.generateSecret(spec);
		Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
		IvParameterSpec IvParameters = new IvParameterSpec(iv);
		cipher.init(Cipher.ENCRYPT_MODE, sec, IvParameters);
		byte[] data = new byte[BUFFER_SIZE];
		int len = 0;
		int count = 0;
		int total = in.available();
		while ((len = in.read(data)) > 0) {
			count += len;
			boolean last = count >= total;
			byte[] written = last?cipher.doFinal(data, 0, len):cipher.update(data, 0, len);
			out.write(written);
		}
		out.flush();
		return count;
	}

	public static byte[] buildSecretKey() throws Exception {
		KeyGenerator keygen = KeyGenerator.getInstance(ALGORITHM);
		return keygen.generateKey().getEncoded();
	}

	public static byte[] randomIVBytes() {
		Random ran = new Random();
		byte[] bytes = new byte[8];
		for (int i = 0; i < bytes.length; ++i) {
			bytes[i] = (byte) ran.nextInt(Byte.MAX_VALUE + 1);
		}
		return bytes;
	}

	/*
	 * 根据字符串生成密钥字节数组
	 * 
	 * @param password 密钥字符串
	 * 
	 * @return
	 * 
	 * @throws UnsupportedEncodingException
	 */
	public static byte[] buildSecretKey(String password)
			throws Exception {
		if(password == null || password.length() == 0) return buildSecretKey();
		byte[] key = new byte[24]; // 声明一个24位的字节数组，默认里面都是0
		byte[] temp = password.getBytes("UTF-8"); // 将字符串转成字节数组

		/*
		 * 执行数组拷贝 System.arraycopy(源数组，从源数组哪里开始拷贝，目标数组，拷贝多少位)
		 */
		System.arraycopy(temp, 0, key, 0, Math.min(key.length,  temp.length));
		return key;
	}

	public static void main(String args[]) throws Exception {
		String plainText = "a12*&1c中文a12*&1c中文a12*&1c中文a12*&1c中文a12*&1c中文a12*&1c中文a12*&1c中文a12*&1c中文a12*&1c中文a12*&1c中文";
		final byte[] secretBytes = DES3Codec.buildSecretKey("hd-doctor-ds3");
		System.out.println(Bytes.bytes2base64(secretBytes));
		final byte[] ivbytes = DES3Codec.randomIVBytes();
		System.out.println("plain text: " + plainText);
		byte[] encrypt = DES3Codec.encrypt(plainText.getBytes(), secretBytes,
				ivbytes);
		System.out.println("cipher text: " + encrypt);
		System.out.println("decrypt text: "
				+ new String(DES3Codec.decrypt(encrypt, secretBytes, ivbytes),
						"UTF-8"));

		UnsafeByteArrayOutputStream s = new UnsafeByteArrayOutputStream(4096);
		DES3Codec.encrypt(
				new java.io.ByteArrayInputStream(plainText.getBytes()), s,
				secretBytes, ivbytes);
		// System.out.println("cipher text: " + encrypt);
		UnsafeByteArrayOutputStream d = new UnsafeByteArrayOutputStream(4096);
		DES3Codec.decrypt(new ByteArrayInputStream(s.toByteArray()), d,
				secretBytes, ivbytes);
		System.out.println("decrypt text: "
				+ new String(d.toByteArray(), "UTF-8"));
	}
}
